package br.usp.pcs.basys2.engine;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import br.usp.pcs.basys2.BasysProcess;
import br.usp.pcs.basys2.event.EndAcessEvent;
import br.usp.pcs.basys2.event.EndOfProcessEvent;
import br.usp.pcs.basys2.event.EndOfProcessException;
import br.usp.pcs.basys2.event.Event;
import br.usp.pcs.basys2.event.InterruptionForFileActivityEvent;
import br.usp.pcs.basys2.event.InterruptionForFileActivityException;
import br.usp.pcs.basys2.event.InterruptionForNewProcessException;
import br.usp.pcs.basys2.event.InterruptionForPeripheralEvent;
import br.usp.pcs.basys2.event.InterruptionForPeripheralException;
import br.usp.pcs.basys2.event.RequestProcessorEvent;
import br.usp.pcs.basys2.event.RequestMemoryEvent;
import br.usp.pcs.basys2.event.TimeSliceReachedEvent;
import br.usp.pcs.basys2.file.FileManager;
import br.usp.pcs.basys2.file.FileManagerListener;
import br.usp.pcs.basys2.file.BasysFile.AcessInfo;
import br.usp.pcs.basys2.info.JobInfo;
import br.usp.pcs.basys2.job.JobManager;
import br.usp.pcs.basys2.job.JobManagerListener;
import br.usp.pcs.basys2.memory.MemoryManager;
import br.usp.pcs.basys2.memory.MemoryManagerListener;
import br.usp.pcs.basys2.peripheral.PeripheralManager;
import br.usp.pcs.basys2.peripheral.PeripheralManagerListener;
import br.usp.pcs.basys2.processor.ProcessorManager;
import br.usp.pcs.basys2.processor.ProcessorManagerListener;

public class FlowManager {

	private MemoryManager memoryManager;
	private ProcessorManager processorManager;
	private PeripheralManager peripheralManager;
	private FileManager fileManager;
	private JobManager jobManager;

	public void defineSystemFlow(BasysSystem system) {
		memoryManager = system.getMemoryManager();
		processorManager = system.getProcessorManager();
		peripheralManager = system.getPeripheralManager();
		fileManager = system.getFileManager();
		jobManager = system.getJobManager();
		defineMemoryManagerFlow();
		defineProcessorManagerFlow();
		definePeripheralManagerFlow();
		defineFileManagerFlow();
		defineJobManagerFlow();
	}

	private void defineJobManagerFlow() {
		jobManager.add(new JobManagerListener() {

			public void onJobIsActivated(JobInfo job) {
				long timeToWait = 20;// TODO Pensar como obter esse dado
				Calendar calendar = getCalendarAfter(timeToWait);
				BasysProcess basysProcess = new BasysProcess(job.getTopProcess());
				RequestMemoryEvent startProcessEvent = new RequestMemoryEvent(basysProcess, calendar,false);
				schedule(startProcessEvent);
			}

			public void onJobIsDeactivated(JobInfo job) {
				
			}

			public void onJobIsQueued(JobInfo job) {
				// TODO Auto-generated method stub
				
			}

		});
	}

	protected void defineMemoryManagerFlow() {
		memoryManager.add(new MemoryManagerListener() {

			public void onProccesIsAllocated(BasysProcess basysProcess) {
				long allocationTime = 200;// TODO Pensar como obter esse dado
				Calendar calendar = getCalendarAfter(allocationTime);
				Event e = new RequestProcessorEvent(basysProcess, calendar,
						processorManager);
				schedule(e);
			}

			public void onProccesIsDeallocated(BasysProcess basysProcess) {
				Engine.getInstance().getSystem().getJobManager().freeProcess(basysProcess);
			}

		});
	}

	protected void defineProcessorManagerFlow() {
		processorManager.add(new ProcessorManagerListener() {

			public void onProcessStartedRunning(BasysProcess basysProcess,
					Integer timeSlice) {
				try {
					basysProcess.runFor(timeSlice);
					Calendar arrivalDate = FlowManager.this
							.getCalendarAfter(timeSlice);
					Event event = new TimeSliceReachedEvent(basysProcess,
							arrivalDate);
					FlowManager.this.schedule(event);
				} catch (InterruptionForNewProcessException exception) {
					long timeToWait = exception.getTimeToWait();
					Calendar arrivalDate = FlowManager.this
							.getCalendarAfter(timeToWait);
					BasysProcess process = exception.getNewProcess();
					Event event = new RequestMemoryEvent(process, arrivalDate,true);
					FlowManager.this.schedule(event);
				} catch (EndOfProcessException exception) {
					long timeToWait = exception.getTimeToWait();
					Calendar arrivalDate = FlowManager.this
							.getCalendarAfter(timeToWait);
					BasysProcess process = exception.getNewProcess();
					Event event = new EndOfProcessEvent(process, arrivalDate);
					FlowManager.this.schedule(event);
				} catch (InterruptionForPeripheralException exception) {
					long timeToWait = exception.getTimeToWait();
					Calendar arrivalDate = FlowManager.this
							.getCalendarAfter(timeToWait);
					BasysProcess process = exception.getProcess();
					Event event = new InterruptionForPeripheralEvent(process,
							arrivalDate, exception.getPeripheralName());
					FlowManager.this.schedule(event);
				} catch (InterruptionForFileActivityException exception) {
					long timeToWait = exception.getTimeToWait();
					Calendar arrivalDate = FlowManager.this
							.getCalendarAfter(timeToWait);
					BasysProcess process = exception.getProcess();
					Event event = new InterruptionForFileActivityEvent(process,
							arrivalDate, exception.getFileActivityInfo());
					FlowManager.this.schedule(event);
				}

			}

			public void onProcessStoppedRunning(final BasysProcess process) {

			}

			public void onProcessIsFinished(BasysProcess basysProcess) {
				Engine.getInstance().getSystem().getMemoryManager().deallocate(basysProcess);
			}
		});
	}

	protected void definePeripheralManagerFlow() {
		peripheralManager
				.addListenerToPeripherals(new PeripheralManagerListener() {

					public void onProcesStartedAccessingPeripheral(
							BasysProcess basysProcess, String peripheralName) {
						long acessTime = peripheralManager.get(peripheralName)
								.getAccessTime();
						Calendar calendar = getCalendarAfter(acessTime);
						Event e = new EndAcessEvent(basysProcess, calendar,
								peripheralName);
						schedule(e);

					}

					public void onProcesStopedAccessingPeripheral(
							BasysProcess basysProcess, String peripheralName) {

						if ("Disk".equals(peripheralName)) {
							AcessInfo acessInfo = processToFileMap
									.get(basysProcess);
							processToFileMap.remove(basysProcess);
							String fileName = acessInfo.getActivityInfo()
									.getFileName();
							fileManager.get(fileName).endAccess(acessInfo);
						}
					}

				});
	}

	private final Map<BasysProcess, AcessInfo> processToFileMap = new HashMap<BasysProcess, AcessInfo>();

	protected void defineFileManagerFlow() {
		fileManager.addListenerToFiles(new FileManagerListener() {

			public void onProcesStartedAccessingFile(AcessInfo acessInfo) {
				long timeToWait = 0L;
				Calendar arrivalDate = FlowManager.this
						.getCalendarAfter(timeToWait);
				BasysProcess process = acessInfo.getProcess();
				Event event = new InterruptionForPeripheralEvent(process,
						arrivalDate, "Disk");
				FlowManager.this.schedule(event);

				processToFileMap.put(process, acessInfo);
			}

			public void onProcesStopedAccessingFile(AcessInfo acessInfo) {
				// TODO Auto-generated method stub

			}

		});
	}

	private void schedule(Event e) {
		Engine.getInstance().schedule(e);
	}

	private Calendar getCalendarAfter(long duration) {
		return Engine.getCalendarAfter(duration);
	}
}
